iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0

我們要測試四件事:

  1. 應該要到設定的時間才執行
  2. 測試重複調用,應該只能調用一次
  3. 測試參數傳遞沒有問題
  4. 測試組件卸載的時候不應該執行
import { renderHook, act } from "@testing-library/react";
import { useDebouncedCallback } from "../src"; // 請根據您的檔案結構進行調整

jest.useFakeTimers(); // 使用 Jest 的模擬定時器

describe("useDebouncedCallback hook", () => {

    //應該要到設定的時間才執行
  it("should call the callback function after the specified delay", () => {
    const callback = jest.fn(); // 創建一個模擬的回調函數
    const delay = 300; // 300ms 的延遲

    const { result } = renderHook(() => useDebouncedCallback(callback, delay));

    act(() => {
      result.current(); // 調用 debounced 函數
    });

    expect(callback).not.toHaveBeenCalled(); // 在延遲時間到達前,不應該調用回調函數

    act(() => {
      jest.advanceTimersByTime(delay); // 快進定時器來觸發回調
    });

    expect(callback).toHaveBeenCalled(); // 在延遲時間到達後,應該調用回調函數
  });

  // 測試重複調用
  it("should only call the callback after the last invocation within the delay", () => {
    const callback = jest.fn();
    const delay = 300;

    const { result } = renderHook(() => useDebouncedCallback(callback, delay));

    act(() => {
      result.current(); // 第一次調用
      jest.advanceTimersByTime(100); // 前進 100ms

      result.current(); // 在延遲時間內的第二次調用
    });

    expect(callback).not.toHaveBeenCalled(); // 延遲時間內不應該調用回調

    act(() => {
      jest.advanceTimersByTime(delay); // 前進到最後一次調用後的 300ms
    });

    expect(callback).toHaveBeenCalledTimes(1); // 最終應該只調用一次回調
  });

  // 測試參數傳遞
  it("should pass the correct arguments to the callback", () => {
    const callback = jest.fn();
    const delay = 300;

    const { result } = renderHook(() => useDebouncedCallback(callback, delay));

    act(() => {
      result.current("arg1", "arg2"); // 調用 debounced 函數並傳遞參數
      jest.advanceTimersByTime(delay); // 前進定時器觸發回調
    });

    expect(callback).toHaveBeenCalledWith("arg1", "arg2"); // 應該使用正確的參數調用回調
  });

  // 測試清理
  it("should clean up the timer when the component is unmounted", () => {
    const callback = jest.fn();
    const delay = 300;

    const { result, unmount } = renderHook(() =>
      useDebouncedCallback(callback, delay)
    );

    act(() => {
      result.current(); // 調用 debounced 函數
    });

    act(() => {
      unmount(); // 卸載組件
    });

    act(() => {
      jest.advanceTimersByTime(delay); // 前進定時器
    });

    expect(callback).not.toHaveBeenCalled(); // 卸載後不應該調用回調
  });
});

advanceTimersByTime 可以模擬快進,跟上次直接完成不一樣,也是一個很方便使用的功能

如果複製貼上這段測試碼,就會發現測試清理這個測試沒有通過,回去看useDebouncedCallback ,我發現我沒有useEffect清除timeout如果卸載組件

加上這個就沒問題了

useEffect(() => {
    return () => {
      if (timerIdRef.current !== null) {
        clearTimeout(timerIdRef.current);
      }
    };
  }, []);

useDebouncedCallback完整程式碼:

import { useRef, useCallback, useEffect } from "react";

export function useDebouncedCallback<T extends (...args: any[]) => any>(
  callback: T,
  delay: number
) {
  const timerIdRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const debouncedCallback = useCallback(
    (...args: Parameters<T>) => {
      if (timerIdRef.current !== null) {
        clearTimeout(timerIdRef.current);
      }

      timerIdRef.current = setTimeout(() => {
        callback(...args);
      }, delay);
    },
    [callback, delay]
  );
  useEffect(() => {
    return () => {
      if (timerIdRef.current !== null) {
        clearTimeout(timerIdRef.current);
      }
    };
  }, []);
  return debouncedCallback;
}

上一篇
[Day 12] useDebounce介紹
下一篇
[Day 14] 常常使用的useOutsideClick
系列文
React進階班,用typescript與jest製作自己的custom hooks庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言